﻿using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Postnuclear.Dependency.Tree;
using Postnuclear.Test.Helpers;

namespace Postnuclear.Test.Dependency.Tree
  {
  /// <summary>
  /// Represents a test class for the <see cref="MemberDependencyElement{THolder, TMember}"/> class.
  /// </summary>
  [TestClass]
  public sealed class MemberDependencyElementTest
    : TestClassBase
    {
    #region Public

    // Methods

    /// <summary>
    /// Initializes this instance.
    /// </summary>
    [TestInitialize]
    public void Initialize()
      {
      ValueProperty = typeof(IValueProvider<int>).GetProperty("Value");
      }

    /// <summary>
    /// A test for <see cref="MemberDependencyElement{THolder, TMember}"/> constructor to check exception for null property.
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(ArgumentNullException))]
    public void CreateWithNullPropertyTest()
      {
      new MemberDependencyElement<IValueProvider<int>, int>(null);
      }

    /// <summary>
    /// A test for <see cref="MemberDependencyElement{THolder, TMember}"/> constructor to check exception for incorrect holder type.
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(ArgumentException))]
    public void CreateWithIncorrectHolderTypeTest()
      {
      new MemberDependencyElement<MemberDependencyElementTest, int>(ValueProperty);
      }

    /// <summary>
    /// A test for <see cref="MemberDependencyElement{THolder, TMember}"/> constructor to check exception for incorrect member type.
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(ArgumentException))]
    public void CreateWithIncorrectMemberTypeTest()
      {
      new MemberDependencyElement<IValueProvider<int>, MemberDependencyElementTest>(ValueProperty);
      }

    /// <summary>
    /// A test for <see cref="MemberDependencyElement{THolder, TMember}.Value" /> property.
    /// </summary>
    [TestMethod]
    public void ValueTest()
      {
      Mock<IValueProvider<int>> valueProvider = new Mock<IValueProvider<int>>();
      valueProvider.SetAndRaise(0);

      MemberDependencyElement<IValueProvider<int>, int> dependency = new MemberDependencyElement<IValueProvider<int>, int>(ValueProperty);
      dependency.UpdateHolderValue(valueProvider.Object);
      Assert.AreEqual(valueProvider.Object.Value, dependency.Value);

      valueProvider.SetAndRaise(valueProvider.Object.Value + 1);
      Assert.AreEqual(valueProvider.Object.Value, dependency.Value);
      }

    /// <summary>
    /// A test for <see cref="INotifyValueChanged.ValueChanged" /> event.
    /// </summary>
    [TestMethod]
    public void ValueChangedTest()
      {
      Mock<IValueProvider<int>> valueProvider = new Mock<IValueProvider<int>>();
      valueProvider.SetAndRaise(1);

      MemberDependencyElement<IValueProvider<int>, int> dependency = new MemberDependencyElement<IValueProvider<int>, int>(ValueProperty);

      int updatesCount = 0;
      EventHandler<EventArgs> updateHandler = (_, __) => ++updatesCount;

      dependency.ValueChanged += updateHandler;
      Assert.AreEqual(0, updatesCount);

      dependency.UpdateHolderValue(valueProvider.Object);
      Assert.AreEqual(0, updatesCount);

      valueProvider.RaiseValueChanged();
      Assert.AreEqual(1, updatesCount);

      valueProvider.RaiseValueChanged();
      Assert.AreEqual(2, updatesCount);

      dependency.ValueChanged -= updateHandler;
      valueProvider.RaiseValueChanged();
      Assert.AreEqual(2, updatesCount);
      }

    /// <summary>
    /// A test for <see cref="INotifyValueChanged.ValueChanged" /> event for the dependency object which implements <see cref="INotifyPropertyChanged"/>.
    /// </summary>
    [TestMethod]
    public void ValueChangedNotifyPropertyChangedTest()
      {
      Mock<IObservableTestInstance<int>> propertyProvider = new Mock<IObservableTestInstance<int>>();
      propertyProvider.SetupGet(m => m.Value).Returns(1);

      PropertyInfo property = typeof(IObservableTestInstance<int>).GetProperty("Value");
      MemberDependencyElement<IObservableTestInstance<int>, int> dependency = new MemberDependencyElement<IObservableTestInstance<int>, int>(property);

      int updatesCount = 0;
      EventHandler<EventArgs> updateHandler = (_, __) => ++updatesCount;

      dependency.ValueChanged += updateHandler;
      Assert.AreEqual(0, updatesCount);

      dependency.UpdateHolderValue(propertyProvider.Object);
      Assert.AreEqual(0, updatesCount);

      propertyProvider.Raise(m => m.PropertyChanged += null, new PropertyChangedEventArgs("Value"));
      Assert.AreEqual(1, updatesCount);

      propertyProvider.Raise(m => m.PropertyChanged += null, new PropertyChangedEventArgs("Values"));
      Assert.AreEqual(1, updatesCount);

      propertyProvider.Raise(m => m.PropertyChanged += null, new PropertyChangedEventArgs("Value"));
      Assert.AreEqual(2, updatesCount);

      dependency.ValueChanged -= updateHandler;
      propertyProvider.Raise(m => m.PropertyChanged += null, new PropertyChangedEventArgs("Value"));
      Assert.AreEqual(2, updatesCount);
      }

    /// <summary>
    /// A test for <see cref="INotifyValueChanged.ValueChanged" /> event for the dependency object which implements <see cref="INotifyCollectionChanged"/>.
    /// </summary>
    [TestMethod]
    public void ValueChangedNotifyCollectionChangedTest()
      {
      Mock<IObservableTestCollection<object>> collection = new Mock<IObservableTestCollection<object>>();
      collection.SetupGet(m => m.Value).Returns(1);

      PropertyInfo property = typeof(IObservableTestCollection<object>).GetProperty("Value");
      MemberDependencyElement<IObservableTestCollection<object>, object> dependency = new MemberDependencyElement<IObservableTestCollection<object>, object>(property);

      int updatesCount = 0;
      EventHandler<EventArgs> updateHandler = (_, __) => ++updatesCount;

      dependency.ValueChanged += updateHandler;
      Assert.AreEqual(0, updatesCount);

      dependency.UpdateHolderValue(collection.Object);
      Assert.AreEqual(0, updatesCount);

      collection.Raise(m => m.CollectionChanged += null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
      Assert.AreEqual(1, updatesCount);

      collection.Raise(m => m.CollectionChanged += null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
      Assert.AreEqual(2, updatesCount);

      dependency.ValueChanged -= updateHandler;
      collection.Raise(m => m.CollectionChanged += null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
      Assert.AreEqual(2, updatesCount);
      }

    #endregion

    #region Private

    // Properties

    private PropertyInfo ValueProperty
      {
      get;
      set;
      }

    #endregion
    }
  }
